package com.wzt.car.service.impl;

import com.wzt.base.constant.BaseConstants;
import com.wzt.base.service.impl.BaseServiceImpl;
import com.wzt.car.domain.Type;
import com.wzt.car.mapper.TypeMapper;
import com.wzt.car.service.ITypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author wzt
 * @since 2022-09-05
 */
@Service
public class TypeServiceImpl extends BaseServiceImpl<Type> implements ITypeService {
    @Autowired
    private TypeMapper typeMapper;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Override
    public List<Type> tree(Long pid) {
        return getTreeByRedis(pid);
    }

    /**
     * 增删改先进行操作，在删除掉缓存，下次查的时候再重新获取缓存
     *
     * @param type
     */
    @Override
    public void add(Type type) {
        super.addOne(type);
        redisTemplate.delete(BaseConstants.CAR_TYPE);
    }

    @Override
    public void remove(Long id) {
        super.removeOne(id);
        redisTemplate.delete(BaseConstants.CAR_TYPE);
    }

    @Override
    public void edit(Type type) {
        super.editOne(type);
        redisTemplate.delete(BaseConstants.CAR_TYPE);
    }

    /**
     * 递归算法
     * SQL执行多，低效率
     *
     * @param pid
     * @return
     */
    public List<Type> getTreeByRecursion(Long pid) {
        List<Type> children = typeMapper.loadChildrenByPid(pid);
        // 递归结束条件
        if (children == null) {
            return null;
        }

        // 递归调用，设置所有的子节点
        for (Type child : children) {
            List<Type> grandson = getTreeByRecursion(child.getId());
            child.setChildren(grandson);
        }
        return children;
    }

    /**
     * 循环算法
     * @param pid
     * @return
     */
    public List<Type> getTreeByLoop(Long pid) {
        // 最终返回的结果集
        List<Type> result = new ArrayList<>(20);

        // 先获取所有的汽车类型
        List<Type> allTypes = typeMapper.loadAll();

        // 将所有的汽车类型放入一个Map，键为该类型的id
        Map<Long, Type> allTypesMap = new HashMap<>();
        for (Type type : allTypes) {
            allTypesMap.put(type.getId(), type);
        }
        // 遍历所有的汽车类型
        allTypes.forEach(type -> {
            // 获取当前类型的父类型id
            Long pidTmp = type.getPid();
            // 如果当前类型的父类型id等于传入的pid，就将当前类型添加到返回集中
            if (pidTmp.longValue() == pid.longValue()) {
                result.add(type);
            } else {
                // 否则从类型Map中获取当前类型的父类型
                Type parent = allTypesMap.get(pidTmp);
                parent.getChildren().add(type); //把自己放入父亲的集合就OK
            }
        });
        return result;
    }

    /**
     * 将汽车类型数据存放到Redis中
     *
     * @param pid
     * @return
     */
    public List<Type> getTreeByRedis(Long pid) {
        Object data = redisTemplate.opsForValue().get(BaseConstants.CAR_TYPE);
        // 如果从Redis缓存中查到的数据不为空，则直接返回查询到的数据
        // 否则从数据库中查询，再将查询到的数据放入到Redis缓存中
        if (data != null) {
            System.out.println("Redis缓存中存在数据~~~~~");
            return (List<Type>) data;
        } else {
            // 加上互斥锁，保证同一时间只有一个线程更新缓存，解决缓存击穿的问题
            synchronized (TypeServiceImpl.class) {
                System.out.println("Redis缓存中不存在该数据~~~~~");
                // 为了解决缓存穿透，即使typeTree为空也要放入缓存中
                List<Type> typeTree = getTreeByLoop(pid);
                redisTemplate.opsForValue().set(BaseConstants.CAR_TYPE, typeTree);
                return typeTree;
            }
        }
    }

    /**
     * 设置主key和备用key解决缓存雪崩的问题
     *
     * @param key
     * @param obj
     */
    private void setCacheData(String key, Object obj) {
        redisTemplate.opsForValue().set(key, obj);
        redisTemplate.opsForValue().set(key + "_STANDBY", obj);
    }

    /**
     * 从缓存获取数据，有三种情况：
     * 1、主key和备用key都没有
     * 2、主key和备用key都有
     * 3、主key有而备用key有（说明执行了增删改操作，需要更新缓存）
     *
     * @param key
     * @param pid
     * @return
     */
    private Object getCacheData(String key, Long pid) {
        // 查询主key
        Object data = redisTemplate.opsForValue().get(key);
        // 如果能从主key数据，则直接返回结果
        if (data != null) {
            return data;
        }

        // 如果主key数据为空，则启用备用key
        data = redisTemplate.opsForValue().get(key + "_STANDBY");
        // 如果备用key数据不为空，则更新缓存
        if (data != null) {
            List<Type> carTypeTree = getTreeByLoop(pid);
            // 设置主key和备用key
            setCacheData(key, carTypeTree);
            return carTypeTree;
        }
        return null;
    }
}
